//
// Copyright(c) 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// entry_points_egl.cpp : Implements the EGL entry points.

#include "libGLESv2/entry_points_egl.h"
#include "libGLESv2/entry_points_egl_ext.h"
#include "libGLESv2/entry_points_gles_2_0_autogen.h"
#include "libGLESv2/entry_points_gles_2_0_ext.h"
#include "libGLESv2/entry_points_gles_3_0_autogen.h"
#include "libGLESv2/entry_points_gles_3_1.h"
#include "libGLESv2/global_state.h"

#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/Surface.h"
#include "libANGLE/Texture.h"
#include "libANGLE/Thread.h"
#include "libANGLE/queryutils.h"
#include "libANGLE/validationEGL.h"

#include "common/debug.h"
#include "common/version.h"

#include "platform/Platform.h"

#include <EGL/eglext.h>

namespace egl
{

namespace
{

void ClipConfigs(const std::vector<const Config *> &filteredConfigs,
                 EGLConfig *output_configs,
                 EGLint config_size,
                 EGLint *num_config)
{
    EGLint result_size = static_cast<EGLint>(filteredConfigs.size());
    if (output_configs)
    {
        result_size = std::max(std::min(result_size, config_size), 0);
        for (EGLint i = 0; i < result_size; i++)
        {
            output_configs[i] = const_cast<Config *>(filteredConfigs[i]);
        }
    }
    *num_config = result_size;
}

}  // anonymous namespace

// EGL 1.0
EGLint EGLAPIENTRY GetError(void)
{
    EVENT("()");
    Thread *thread = GetCurrentThread();

    EGLint error = thread->getError();
    thread->setError(NoError());
    return error;
}

EGLDisplay EGLAPIENTRY GetDisplay(EGLNativeDisplayType display_id)
{
    EVENT("(EGLNativeDisplayType display_id = 0x%0.8p)", display_id);

    return Display::GetDisplayFromNativeDisplay(display_id, AttributeMap());
}

EGLBoolean EGLAPIENTRY Initialize(EGLDisplay dpy, EGLint *major, EGLint *minor)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint *major = 0x%0.8p, EGLint *minor = 0x%0.8p)", dpy,
          major, minor);
    Thread *thread = GetCurrentThread();

    Display *display = static_cast<Display *>(dpy);
    if (dpy == EGL_NO_DISPLAY || !Display::isValidDisplay(display))
    {
        thread->setError(EglBadDisplay());
        return EGL_FALSE;
    }

    Error error = display->initialize();
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    if (major)
        *major = 1;
    if (minor)
        *minor = 4;

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY Terminate(EGLDisplay dpy)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p)", dpy);
    Thread *thread = GetCurrentThread();

    Display *display = static_cast<Display *>(dpy);
    if (dpy == EGL_NO_DISPLAY || !Display::isValidDisplay(display))
    {
        thread->setError(EglBadDisplay());
        return EGL_FALSE;
    }

    if (display->isValidContext(thread->getContext()))
    {
        thread->setCurrent(nullptr);
    }

    Error error = display->terminate();
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

const char *EGLAPIENTRY QueryString(EGLDisplay dpy, EGLint name)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint name = %d)", dpy, name);
    Thread *thread = GetCurrentThread();

    Display *display = static_cast<Display *>(dpy);
    if (!(display == EGL_NO_DISPLAY && name == EGL_EXTENSIONS))
    {
        Error error = ValidateDisplay(display);
        if (error.isError())
        {
            thread->setError(error);
            return nullptr;
        }
    }

    const char *result;
    switch (name)
    {
        case EGL_CLIENT_APIS:
            result = "OpenGL_ES";
            break;
        case EGL_EXTENSIONS:
            if (display == EGL_NO_DISPLAY)
            {
                result = Display::GetClientExtensionString().c_str();
            }
            else
            {
                result = display->getExtensionString().c_str();
            }
            break;
        case EGL_VENDOR:
            result = display->getVendorString().c_str();
            break;
        case EGL_VERSION:
            result = "1.4 (ANGLE " ANGLE_VERSION_STRING ")";
            break;
        default:
            thread->setError(EglBadParameter());
            return nullptr;
    }

    thread->setError(NoError());
    return result;
}

EGLBoolean EGLAPIENTRY GetConfigs(EGLDisplay dpy,
                                  EGLConfig *configs,
                                  EGLint config_size,
                                  EGLint *num_config)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLConfig *configs = 0x%0.8p, "
        "EGLint config_size = %d, EGLint *num_config = 0x%0.8p)",
        dpy, configs, config_size, num_config);
    Thread *thread = GetCurrentThread();

    Display *display = static_cast<Display *>(dpy);

    Error error = ValidateGetConfigs(display, config_size, num_config);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    ClipConfigs(display->getConfigs(AttributeMap()), configs, config_size, num_config);

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY ChooseConfig(EGLDisplay dpy,
                                    const EGLint *attrib_list,
                                    EGLConfig *configs,
                                    EGLint config_size,
                                    EGLint *num_config)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, const EGLint *attrib_list = 0x%0.8p, "
        "EGLConfig *configs = 0x%0.8p, EGLint config_size = %d, EGLint *num_config = 0x%0.8p)",
        dpy, attrib_list, configs, config_size, num_config);
    Thread *thread = GetCurrentThread();

    Display *display       = static_cast<Display *>(dpy);
    AttributeMap attribMap = AttributeMap::CreateFromIntArray(attrib_list);

    Error error = ValidateChooseConfig(display, attribMap, config_size, num_config);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    ClipConfigs(display->getConfigs(attribMap), configs, config_size, num_config);

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY GetConfigAttrib(EGLDisplay dpy,
                                       EGLConfig config,
                                       EGLint attribute,
                                       EGLint *value)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLConfig config = 0x%0.8p, EGLint attribute = %d, EGLint "
        "*value = 0x%0.8p)",
        dpy, config, attribute, value);
    Thread *thread = GetCurrentThread();

    Display *display      = static_cast<Display *>(dpy);
    Config *configuration = static_cast<Config *>(config);

    Error error = ValidateGetConfigAttrib(display, configuration, attribute);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    QueryConfigAttrib(configuration, attribute, value);

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLSurface EGLAPIENTRY CreateWindowSurface(EGLDisplay dpy,
                                           EGLConfig config,
                                           EGLNativeWindowType win,
                                           const EGLint *attrib_list)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLConfig config = 0x%0.8p, EGLNativeWindowType win = 0x%0.8p, "
        "const EGLint *attrib_list = 0x%0.8p)",
        dpy, config, win, attrib_list);
    Thread *thread = GetCurrentThread();

    Display *display        = static_cast<Display *>(dpy);
    Config *configuration   = static_cast<Config *>(config);
    AttributeMap attributes = AttributeMap::CreateFromIntArray(attrib_list);

    Error error = ValidateCreateWindowSurface(display, configuration, win, attributes);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_NO_SURFACE;
    }

    egl::Surface *surface = nullptr;
    error                 = display->createWindowSurface(configuration, win, attributes, &surface);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_NO_SURFACE;
    }

    return static_cast<EGLSurface>(surface);
}

EGLSurface EGLAPIENTRY CreatePbufferSurface(EGLDisplay dpy,
                                            EGLConfig config,
                                            const EGLint *attrib_list)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLConfig config = 0x%0.8p, const EGLint *attrib_list = "
        "0x%0.8p)",
        dpy, config, attrib_list);
    Thread *thread = GetCurrentThread();

    Display *display        = static_cast<Display *>(dpy);
    Config *configuration   = static_cast<Config *>(config);
    AttributeMap attributes = AttributeMap::CreateFromIntArray(attrib_list);

    Error error = ValidateCreatePbufferSurface(display, configuration, attributes);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_NO_SURFACE;
    }

    egl::Surface *surface = nullptr;
    error                 = display->createPbufferSurface(configuration, attributes, &surface);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_NO_SURFACE;
    }

    return static_cast<EGLSurface>(surface);
}

EGLSurface EGLAPIENTRY CreatePixmapSurface(EGLDisplay dpy,
                                           EGLConfig config,
                                           EGLNativePixmapType pixmap,
                                           const EGLint *attrib_list)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLConfig config = 0x%0.8p, EGLNativePixmapType pixmap = "
        "0x%0.8p, "
        "const EGLint *attrib_list = 0x%0.8p)",
        dpy, config, pixmap, attrib_list);
    Thread *thread = GetCurrentThread();

    Display *display      = static_cast<Display *>(dpy);
    Config *configuration = static_cast<Config *>(config);

    Error error = ValidateConfig(display, configuration);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_NO_SURFACE;
    }

    UNIMPLEMENTED();  // FIXME

    thread->setError(NoError());
    return EGL_NO_SURFACE;
}

EGLBoolean EGLAPIENTRY DestroySurface(EGLDisplay dpy, EGLSurface surface)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLSurface surface = 0x%0.8p)", dpy, surface);
    Thread *thread = GetCurrentThread();

    Display *display    = static_cast<Display *>(dpy);
    Surface *eglSurface = static_cast<Surface *>(surface);

    Error error = ValidateSurface(display, eglSurface);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    if (surface == EGL_NO_SURFACE)
    {
        thread->setError(EglBadSurface());
        return EGL_FALSE;
    }

    error = display->destroySurface(reinterpret_cast<Surface *>(surface));
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY QuerySurface(EGLDisplay dpy,
                                    EGLSurface surface,
                                    EGLint attribute,
                                    EGLint *value)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLSurface surface = 0x%0.8p, EGLint attribute = %d, EGLint "
        "*value = 0x%0.8p)",
        dpy, surface, attribute, value);
    Thread *thread = GetCurrentThread();

    const Display *display    = static_cast<const Display *>(dpy);
    const Surface *eglSurface = static_cast<const Surface *>(surface);

    Error error = ValidateQuerySurface(display, eglSurface, attribute, value);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    QuerySurfaceAttrib(eglSurface, attribute, value);

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLContext EGLAPIENTRY CreateContext(EGLDisplay dpy,
                                     EGLConfig config,
                                     EGLContext share_context,
                                     const EGLint *attrib_list)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLConfig config = 0x%0.8p, EGLContext share_context = "
        "0x%0.8p, "
        "const EGLint *attrib_list = 0x%0.8p)",
        dpy, config, share_context, attrib_list);
    Thread *thread = GetCurrentThread();

    Display *display             = static_cast<Display *>(dpy);
    Config *configuration        = static_cast<Config *>(config);
    gl::Context *sharedGLContext = static_cast<gl::Context *>(share_context);
    AttributeMap attributes      = AttributeMap::CreateFromIntArray(attrib_list);

    Error error = ValidateCreateContext(display, configuration, sharedGLContext, attributes);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_NO_CONTEXT;
    }

    gl::Context *context = nullptr;
    error = display->createContext(configuration, sharedGLContext, attributes, &context);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_NO_CONTEXT;
    }

    thread->setError(NoError());
    return static_cast<EGLContext>(context);
}

EGLBoolean EGLAPIENTRY DestroyContext(EGLDisplay dpy, EGLContext ctx)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLContext ctx = 0x%0.8p)", dpy, ctx);
    Thread *thread = GetCurrentThread();

    Display *display     = static_cast<Display *>(dpy);
    gl::Context *context = static_cast<gl::Context *>(ctx);

    Error error = ValidateContext(display, context);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    if (ctx == EGL_NO_CONTEXT)
    {
        thread->setError(EglBadContext());
        return EGL_FALSE;
    }

    if (context == thread->getContext())
    {
        thread->setCurrent(nullptr);
    }

    error = display->destroyContext(context);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLSurface draw = 0x%0.8p, EGLSurface read = 0x%0.8p, "
        "EGLContext ctx = 0x%0.8p)",
        dpy, draw, read, ctx);
    Thread *thread = GetCurrentThread();

    Display *display     = static_cast<Display *>(dpy);
    gl::Context *context = static_cast<gl::Context *>(ctx);

    Error error = ValidateMakeCurrent(display, draw, read, context);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    Surface *readSurface   = static_cast<Surface *>(read);
    Surface *drawSurface   = static_cast<Surface *>(draw);
    Error makeCurrentError = display->makeCurrent(drawSurface, readSurface, context);
    if (makeCurrentError.isError())
    {
        thread->setError(makeCurrentError);
        return EGL_FALSE;
    }

    gl::Context *previousContext = thread->getContext();
    thread->setCurrent(context);

    // Release the surface from the previously-current context, to allow
    // destroyed surfaces to delete themselves.
    if (previousContext != nullptr && context != previousContext)
    {
        auto err = previousContext->releaseSurface(display);
        if (err.isError())
        {
            thread->setError(err);
            return EGL_FALSE;
        }
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLSurface EGLAPIENTRY GetCurrentSurface(EGLint readdraw)
{
    EVENT("(EGLint readdraw = %d)", readdraw);
    Thread *thread = GetCurrentThread();

    if (readdraw == EGL_READ)
    {
        thread->setError(NoError());
        return thread->getCurrentReadSurface();
    }
    else if (readdraw == EGL_DRAW)
    {
        thread->setError(NoError());
        return thread->getCurrentDrawSurface();
    }
    else
    {
        thread->setError(EglBadParameter());
        return EGL_NO_SURFACE;
    }
}

EGLDisplay EGLAPIENTRY GetCurrentDisplay(void)
{
    EVENT("()");
    Thread *thread = GetCurrentThread();

    thread->setError(NoError());
    if (thread->getContext() != nullptr)
    {
        return thread->getContext()->getCurrentDisplay();
    }
    return EGL_NO_DISPLAY;
}

EGLBoolean EGLAPIENTRY QueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLContext ctx = 0x%0.8p, EGLint attribute = %d, EGLint *value "
        "= 0x%0.8p)",
        dpy, ctx, attribute, value);
    Thread *thread = GetCurrentThread();

    Display *display     = static_cast<Display *>(dpy);
    gl::Context *context = static_cast<gl::Context *>(ctx);

    Error error = ValidateQueryContext(display, context, attribute, value);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    QueryContextAttrib(context, attribute, value);

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY WaitGL(void)
{
    EVENT("()");
    Thread *thread = GetCurrentThread();

    Display *display = thread->getCurrentDisplay();

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    // eglWaitGL like calling eglWaitClient with the OpenGL ES API bound. Since we only implement
    // OpenGL ES we can do the call directly.
    error = display->waitClient(thread->getContext());
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY WaitNative(EGLint engine)
{
    EVENT("(EGLint engine = %d)", engine);
    Thread *thread = GetCurrentThread();

    Display *display = thread->getCurrentDisplay();

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    if (engine != EGL_CORE_NATIVE_ENGINE)
    {
        thread->setError(EglBadParameter() << "the 'engine' parameter has an unrecognized value");
    }

    error = display->waitNative(thread->getContext(), engine);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY SwapBuffers(EGLDisplay dpy, EGLSurface surface)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLSurface surface = 0x%0.8p)", dpy, surface);
    Thread *thread = GetCurrentThread();

    Display *display    = static_cast<Display *>(dpy);
    Surface *eglSurface = (Surface *)surface;

    Error error = ValidateSurface(display, eglSurface);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    if (display->testDeviceLost())
    {
        thread->setError(EglContextLost());
        return EGL_FALSE;
    }

    if (surface == EGL_NO_SURFACE)
    {
        thread->setError(EglBadSurface());
        return EGL_FALSE;
    }

    if (!thread->getContext() || thread->getCurrentDrawSurface() != eglSurface)
    {
        thread->setError(EglBadSurface());
        return EGL_FALSE;
    }

    error = eglSurface->swap(thread->getContext());
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY CopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLSurface surface = 0x%0.8p, EGLNativePixmapType target = "
        "0x%0.8p)",
        dpy, surface, target);
    Thread *thread = GetCurrentThread();

    Display *display    = static_cast<Display *>(dpy);
    Surface *eglSurface = static_cast<Surface *>(surface);

    Error error = ValidateSurface(display, eglSurface);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    if (display->testDeviceLost())
    {
        thread->setError(EglContextLost());
        return EGL_FALSE;
    }

    UNIMPLEMENTED();  // FIXME

    thread->setError(NoError());
    return 0;
}

// EGL 1.1
EGLBoolean EGLAPIENTRY BindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLSurface surface = 0x%0.8p, EGLint buffer = %d)", dpy,
          surface, buffer);
    Thread *thread = GetCurrentThread();

    Display *display    = static_cast<Display *>(dpy);
    Surface *eglSurface = static_cast<Surface *>(surface);

    Error error = ValidateSurface(display, eglSurface);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    if (buffer != EGL_BACK_BUFFER)
    {
        thread->setError(EglBadParameter());
        return EGL_FALSE;
    }

    if (surface == EGL_NO_SURFACE || eglSurface->getType() == EGL_WINDOW_BIT)
    {
        thread->setError(EglBadSurface());
        return EGL_FALSE;
    }

    if (eglSurface->getBoundTexture())
    {
        thread->setError(EglBadAccess());
        return EGL_FALSE;
    }

    if (eglSurface->getTextureFormat() == EGL_NO_TEXTURE)
    {
        thread->setError(EglBadMatch());
        return EGL_FALSE;
    }

    gl::Context *context = thread->getContext();
    if (context)
    {
        gl::Texture *textureObject = context->getTargetTexture(GL_TEXTURE_2D);
        ASSERT(textureObject != nullptr);

        if (textureObject->getImmutableFormat())
        {
            thread->setError(EglBadMatch());
            return EGL_FALSE;
        }

        error = eglSurface->bindTexImage(context, textureObject, buffer);
        if (error.isError())
        {
            thread->setError(error);
            return EGL_FALSE;
        }
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY SurfaceAttrib(EGLDisplay dpy,
                                     EGLSurface surface,
                                     EGLint attribute,
                                     EGLint value)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLSurface surface = 0x%0.8p, EGLint attribute = %d, EGLint "
        "value = %d)",
        dpy, surface, attribute, value);
    Thread *thread = GetCurrentThread();

    Display *display    = static_cast<Display *>(dpy);
    Surface *eglSurface = static_cast<Surface *>(surface);

    Error error = ValidateSurfaceAttrib(display, eglSurface, attribute, value);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    SetSurfaceAttrib(eglSurface, attribute, value);

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY ReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLSurface surface = 0x%0.8p, EGLint buffer = %d)", dpy,
          surface, buffer);
    Thread *thread = GetCurrentThread();

    Display *display    = static_cast<Display *>(dpy);
    Surface *eglSurface = static_cast<Surface *>(surface);

    Error error = ValidateSurface(display, eglSurface);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    if (buffer != EGL_BACK_BUFFER)
    {
        thread->setError(EglBadParameter());
        return EGL_FALSE;
    }

    if (surface == EGL_NO_SURFACE || eglSurface->getType() == EGL_WINDOW_BIT)
    {
        thread->setError(EglBadSurface());
        return EGL_FALSE;
    }

    if (eglSurface->getTextureFormat() == EGL_NO_TEXTURE)
    {
        thread->setError(EglBadMatch());
        return EGL_FALSE;
    }

    gl::Texture *texture = eglSurface->getBoundTexture();

    if (texture)
    {
        error = eglSurface->releaseTexImage(thread->getContext(), buffer);
        if (error.isError())
        {
            thread->setError(error);
            return EGL_FALSE;
        }
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY SwapInterval(EGLDisplay dpy, EGLint interval)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint interval = %d)", dpy, interval);
    Thread *thread = GetCurrentThread();

    Display *display = static_cast<Display *>(dpy);

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    Surface *draw_surface = static_cast<Surface *>(thread->getCurrentDrawSurface());

    if (draw_surface == nullptr)
    {
        thread->setError(EglBadSurface());
        return EGL_FALSE;
    }

    const egl::Config *surfaceConfig = draw_surface->getConfig();
    EGLint clampedInterval           = std::min(std::max(interval, surfaceConfig->minSwapInterval),
                                      surfaceConfig->maxSwapInterval);

    draw_surface->setSwapInterval(clampedInterval);

    thread->setError(NoError());
    return EGL_TRUE;
}

// EGL 1.2
EGLBoolean EGLAPIENTRY BindAPI(EGLenum api)
{
    EVENT("(EGLenum api = 0x%X)", api);
    Thread *thread = GetCurrentThread();

    switch (api)
    {
        case EGL_OPENGL_API:
        case EGL_OPENVG_API:
            thread->setError(EglBadParameter());
            return EGL_FALSE;  // Not supported by this implementation
        case EGL_OPENGL_ES_API:
            break;
        default:
            thread->setError(EglBadParameter());
            return EGL_FALSE;
    }

    thread->setAPI(api);

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLenum EGLAPIENTRY QueryAPI(void)
{
    EVENT("()");
    Thread *thread = GetCurrentThread();

    EGLenum API = thread->getAPI();

    thread->setError(NoError());
    return API;
}

EGLSurface EGLAPIENTRY CreatePbufferFromClientBuffer(EGLDisplay dpy,
                                                     EGLenum buftype,
                                                     EGLClientBuffer buffer,
                                                     EGLConfig config,
                                                     const EGLint *attrib_list)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLenum buftype = 0x%X, EGLClientBuffer buffer = 0x%0.8p, "
        "EGLConfig config = 0x%0.8p, const EGLint *attrib_list = 0x%0.8p)",
        dpy, buftype, buffer, config, attrib_list);
    Thread *thread = GetCurrentThread();

    Display *display        = static_cast<Display *>(dpy);
    Config *configuration   = static_cast<Config *>(config);
    AttributeMap attributes = AttributeMap::CreateFromIntArray(attrib_list);

    Error error =
        ValidateCreatePbufferFromClientBuffer(display, buftype, buffer, configuration, attributes);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_NO_SURFACE;
    }

    egl::Surface *surface = nullptr;
    error = display->createPbufferFromClientBuffer(configuration, buftype, buffer, attributes,
                                                   &surface);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_NO_SURFACE;
    }

    return static_cast<EGLSurface>(surface);
}

EGLBoolean EGLAPIENTRY ReleaseThread(void)
{
    EVENT("()");
    Thread *thread = GetCurrentThread();

    MakeCurrent(EGL_NO_DISPLAY, EGL_NO_CONTEXT, EGL_NO_SURFACE, EGL_NO_SURFACE);

    thread->setError(NoError());
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY WaitClient(void)
{
    EVENT("()");
    Thread *thread = GetCurrentThread();

    Display *display = thread->getCurrentDisplay();

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    error = display->waitClient(thread->getContext());
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}

// EGL 1.4
EGLContext EGLAPIENTRY GetCurrentContext(void)
{
    EVENT("()");
    Thread *thread = GetCurrentThread();

    gl::Context *context = thread->getContext();

    thread->setError(NoError());
    return static_cast<EGLContext>(context);
}

// EGL 1.5
EGLSync EGLAPIENTRY CreateSync(EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLenum type = 0x%X, const EGLint* attrib_list = 0x%0.8p)",
          dpy, type, attrib_list);
    Thread *thread = GetCurrentThread();

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglCreateSync unimplemented.");
    return EGL_NO_SYNC;
}

EGLBoolean EGLAPIENTRY DestroySync(EGLDisplay dpy, EGLSync sync)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLSync sync = 0x%0.8p)", dpy, sync);
    Thread *thread = GetCurrentThread();

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglDestroySync unimplemented.");
    return EGL_FALSE;
}

EGLint EGLAPIENTRY ClientWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLSync sync = 0x%0.8p, EGLint flags = 0x%X, EGLTime timeout = "
        "%d)",
        dpy, sync, flags, timeout);
    Thread *thread = GetCurrentThread();

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglClientWaitSync unimplemented.");
    return 0;
}

EGLBoolean EGLAPIENTRY GetSyncAttrib(EGLDisplay dpy,
                                     EGLSync sync,
                                     EGLint attribute,
                                     EGLAttrib *value)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLSync sync = 0x%0.8p, EGLint attribute = 0x%X, EGLAttrib "
        "*value = 0x%0.8p)",
        dpy, sync, attribute, value);
    Thread *thread = GetCurrentThread();

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglSyncAttrib unimplemented.");
    return EGL_FALSE;
}

EGLImage EGLAPIENTRY CreateImage(EGLDisplay dpy,
                                 EGLContext ctx,
                                 EGLenum target,
                                 EGLClientBuffer buffer,
                                 const EGLAttrib *attrib_list)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLContext ctx = 0x%0.8p, EGLenum target = 0x%X, "
        "EGLClientBuffer buffer = 0x%0.8p, const EGLAttrib *attrib_list = 0x%0.8p)",
        dpy, ctx, target, buffer, attrib_list);
    Thread *thread = GetCurrentThread();

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglCreateImage unimplemented.");
    return EGL_NO_IMAGE;
}

EGLBoolean EGLAPIENTRY DestroyImage(EGLDisplay dpy, EGLImage image)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLImage image = 0x%0.8p)", dpy, image);
    Thread *thread = GetCurrentThread();

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglDestroyImage unimplemented.");
    return EGL_FALSE;
}

EGLDisplay EGLAPIENTRY GetPlatformDisplay(EGLenum platform,
                                          void *native_display,
                                          const EGLAttrib *attrib_list)
{
    EVENT(
        "(EGLenum platform = %d, void* native_display = 0x%0.8p, const EGLint* attrib_list = "
        "0x%0.8p)",
        platform, native_display, attrib_list);
    Thread *thread = GetCurrentThread();

    Error err = ValidateGetPlatformDisplay(platform, native_display, attrib_list);
    thread->setError(err);
    if (err.isError())
    {
        return EGL_NO_DISPLAY;
    }

    const auto &attribMap = AttributeMap::CreateFromAttribArray(attrib_list);
    if (platform == EGL_PLATFORM_ANGLE_ANGLE)
    {
        return Display::GetDisplayFromNativeDisplay(
            gl::bitCast<EGLNativeDisplayType>(native_display), attribMap);
    }
    else if (platform == EGL_PLATFORM_DEVICE_EXT)
    {
        Device *eglDevice = reinterpret_cast<Device *>(native_display);
        return Display::GetDisplayFromDevice(eglDevice, attribMap);
    }
    else
    {
        UNREACHABLE();
        return EGL_NO_DISPLAY;
    }
}

EGLSurface EGLAPIENTRY CreatePlatformWindowSurface(EGLDisplay dpy,
                                                   EGLConfig config,
                                                   void *native_window,
                                                   const EGLAttrib *attrib_list)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLConfig config = 0x%0.8p, void* native_window = 0x%0.8p, "
        "const EGLint* attrib_list = 0x%0.8p)",
        dpy, config, native_window, attrib_list);
    Thread *thread = GetCurrentThread();

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglCreatePlatformWindowSurface unimplemented.");
    return EGL_NO_SURFACE;
}

EGLSurface EGLAPIENTRY CreatePlatformPixmapSurface(EGLDisplay dpy,
                                                   EGLConfig config,
                                                   void *native_pixmap,
                                                   const EGLAttrib *attrib_list)
{
    EVENT(
        "(EGLDisplay dpy = 0x%0.8p, EGLConfig config = 0x%0.8p, void* native_pixmap = 0x%0.8p, "
        "const EGLint* attrib_list = 0x%0.8p)",
        dpy, config, native_pixmap, attrib_list);
    Thread *thread = GetCurrentThread();

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglCreatePlatformPixmapSurface unimplemented.");
    return EGL_NO_SURFACE;
}

EGLBoolean EGLAPIENTRY WaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLSync sync = 0x%0.8p, EGLint flags = 0x%X)", dpy, sync,
          flags);
    Thread *thread = GetCurrentThread();

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglWaitSync unimplemented.");
    return EGL_FALSE;
}

__eglMustCastToProperFunctionPointerType EGLAPIENTRY GetProcAddress(const char *procname)
{
    EVENT("(const char *procname = \"%s\")", procname);
    Thread *thread = GetCurrentThread();

    typedef std::map<std::string, __eglMustCastToProperFunctionPointerType> ProcAddressMap;
    auto generateProcAddressMap = []() {
        ProcAddressMap map;
#define INSERT_PROC_ADDRESS(ns, proc) \
    map[#ns #proc] = reinterpret_cast<__eglMustCastToProperFunctionPointerType>(ns::proc)

#define INSERT_PROC_ADDRESS_NO_NS(name, proc) \
    map[name] = reinterpret_cast<__eglMustCastToProperFunctionPointerType>(proc)

        // GLES2 core
        INSERT_PROC_ADDRESS(gl, ActiveTexture);
        INSERT_PROC_ADDRESS(gl, AttachShader);
        INSERT_PROC_ADDRESS(gl, BindAttribLocation);
        INSERT_PROC_ADDRESS(gl, BindBuffer);
        INSERT_PROC_ADDRESS(gl, BindFramebuffer);
        INSERT_PROC_ADDRESS(gl, BindRenderbuffer);
        INSERT_PROC_ADDRESS(gl, BindTexture);
        INSERT_PROC_ADDRESS(gl, BlendColor);
        INSERT_PROC_ADDRESS(gl, BlendEquation);
        INSERT_PROC_ADDRESS(gl, BlendEquationSeparate);
        INSERT_PROC_ADDRESS(gl, BlendFunc);
        INSERT_PROC_ADDRESS(gl, BlendFuncSeparate);
        INSERT_PROC_ADDRESS(gl, BufferData);
        INSERT_PROC_ADDRESS(gl, BufferSubData);
        INSERT_PROC_ADDRESS(gl, CheckFramebufferStatus);
        INSERT_PROC_ADDRESS(gl, Clear);
        INSERT_PROC_ADDRESS(gl, ClearColor);
        INSERT_PROC_ADDRESS(gl, ClearDepthf);
        INSERT_PROC_ADDRESS(gl, ClearStencil);
        INSERT_PROC_ADDRESS(gl, ColorMask);
        INSERT_PROC_ADDRESS(gl, CompileShader);
        INSERT_PROC_ADDRESS(gl, CompressedTexImage2D);
        INSERT_PROC_ADDRESS(gl, CompressedTexSubImage2D);
        INSERT_PROC_ADDRESS(gl, CopyTexImage2D);
        INSERT_PROC_ADDRESS(gl, CopyTexSubImage2D);
        INSERT_PROC_ADDRESS(gl, CreateProgram);
        INSERT_PROC_ADDRESS(gl, CreateShader);
        INSERT_PROC_ADDRESS(gl, CullFace);
        INSERT_PROC_ADDRESS(gl, DeleteBuffers);
        INSERT_PROC_ADDRESS(gl, DeleteFramebuffers);
        INSERT_PROC_ADDRESS(gl, DeleteProgram);
        INSERT_PROC_ADDRESS(gl, DeleteRenderbuffers);
        INSERT_PROC_ADDRESS(gl, DeleteShader);
        INSERT_PROC_ADDRESS(gl, DeleteTextures);
        INSERT_PROC_ADDRESS(gl, DepthFunc);
        INSERT_PROC_ADDRESS(gl, DepthMask);
        INSERT_PROC_ADDRESS(gl, DepthRangef);
        INSERT_PROC_ADDRESS(gl, DetachShader);
        INSERT_PROC_ADDRESS(gl, Disable);
        INSERT_PROC_ADDRESS(gl, DisableVertexAttribArray);
        INSERT_PROC_ADDRESS(gl, DrawArrays);
        INSERT_PROC_ADDRESS(gl, DrawElements);
        INSERT_PROC_ADDRESS(gl, Enable);
        INSERT_PROC_ADDRESS(gl, EnableVertexAttribArray);
        INSERT_PROC_ADDRESS(gl, Finish);
        INSERT_PROC_ADDRESS(gl, Flush);
        INSERT_PROC_ADDRESS(gl, FramebufferRenderbuffer);
        INSERT_PROC_ADDRESS(gl, FramebufferTexture2D);
        INSERT_PROC_ADDRESS(gl, FrontFace);
        INSERT_PROC_ADDRESS(gl, GenBuffers);
        INSERT_PROC_ADDRESS(gl, GenerateMipmap);
        INSERT_PROC_ADDRESS(gl, GenFramebuffers);
        INSERT_PROC_ADDRESS(gl, GenRenderbuffers);
        INSERT_PROC_ADDRESS(gl, GenTextures);
        INSERT_PROC_ADDRESS(gl, GetActiveAttrib);
        INSERT_PROC_ADDRESS(gl, GetActiveUniform);
        INSERT_PROC_ADDRESS(gl, GetAttachedShaders);
        INSERT_PROC_ADDRESS(gl, GetAttribLocation);
        INSERT_PROC_ADDRESS(gl, GetBooleanv);
        INSERT_PROC_ADDRESS(gl, GetBufferParameteriv);
        INSERT_PROC_ADDRESS(gl, GetError);
        INSERT_PROC_ADDRESS(gl, GetFloatv);
        INSERT_PROC_ADDRESS(gl, GetFramebufferAttachmentParameteriv);
        INSERT_PROC_ADDRESS(gl, GetIntegerv);
        INSERT_PROC_ADDRESS(gl, GetProgramiv);
        INSERT_PROC_ADDRESS(gl, GetProgramInfoLog);
        INSERT_PROC_ADDRESS(gl, GetRenderbufferParameteriv);
        INSERT_PROC_ADDRESS(gl, GetShaderiv);
        INSERT_PROC_ADDRESS(gl, GetShaderInfoLog);
        INSERT_PROC_ADDRESS(gl, GetShaderPrecisionFormat);
        INSERT_PROC_ADDRESS(gl, GetShaderSource);
        INSERT_PROC_ADDRESS(gl, GetString);
        INSERT_PROC_ADDRESS(gl, GetTexParameterfv);
        INSERT_PROC_ADDRESS(gl, GetTexParameteriv);
        INSERT_PROC_ADDRESS(gl, GetUniformfv);
        INSERT_PROC_ADDRESS(gl, GetUniformiv);
        INSERT_PROC_ADDRESS(gl, GetUniformLocation);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribfv);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribiv);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribPointerv);
        INSERT_PROC_ADDRESS(gl, Hint);
        INSERT_PROC_ADDRESS(gl, IsBuffer);
        INSERT_PROC_ADDRESS(gl, IsEnabled);
        INSERT_PROC_ADDRESS(gl, IsFramebuffer);
        INSERT_PROC_ADDRESS(gl, IsProgram);
        INSERT_PROC_ADDRESS(gl, IsRenderbuffer);
        INSERT_PROC_ADDRESS(gl, IsShader);
        INSERT_PROC_ADDRESS(gl, IsTexture);
        INSERT_PROC_ADDRESS(gl, LineWidth);
        INSERT_PROC_ADDRESS(gl, LinkProgram);
        INSERT_PROC_ADDRESS(gl, PixelStorei);
        INSERT_PROC_ADDRESS(gl, PolygonOffset);
        INSERT_PROC_ADDRESS(gl, ReadPixels);
        INSERT_PROC_ADDRESS(gl, ReleaseShaderCompiler);
        INSERT_PROC_ADDRESS(gl, RenderbufferStorage);
        INSERT_PROC_ADDRESS(gl, SampleCoverage);
        INSERT_PROC_ADDRESS(gl, Scissor);
        INSERT_PROC_ADDRESS(gl, ShaderBinary);
        INSERT_PROC_ADDRESS(gl, ShaderSource);
        INSERT_PROC_ADDRESS(gl, StencilFunc);
        INSERT_PROC_ADDRESS(gl, StencilFuncSeparate);
        INSERT_PROC_ADDRESS(gl, StencilMask);
        INSERT_PROC_ADDRESS(gl, StencilMaskSeparate);
        INSERT_PROC_ADDRESS(gl, StencilOp);
        INSERT_PROC_ADDRESS(gl, StencilOpSeparate);
        INSERT_PROC_ADDRESS(gl, TexImage2D);
        INSERT_PROC_ADDRESS(gl, TexParameterf);
        INSERT_PROC_ADDRESS(gl, TexParameterfv);
        INSERT_PROC_ADDRESS(gl, TexParameteri);
        INSERT_PROC_ADDRESS(gl, TexParameteriv);
        INSERT_PROC_ADDRESS(gl, TexSubImage2D);
        INSERT_PROC_ADDRESS(gl, Uniform1f);
        INSERT_PROC_ADDRESS(gl, Uniform1fv);
        INSERT_PROC_ADDRESS(gl, Uniform1i);
        INSERT_PROC_ADDRESS(gl, Uniform1iv);
        INSERT_PROC_ADDRESS(gl, Uniform2f);
        INSERT_PROC_ADDRESS(gl, Uniform2fv);
        INSERT_PROC_ADDRESS(gl, Uniform2i);
        INSERT_PROC_ADDRESS(gl, Uniform2iv);
        INSERT_PROC_ADDRESS(gl, Uniform3f);
        INSERT_PROC_ADDRESS(gl, Uniform3fv);
        INSERT_PROC_ADDRESS(gl, Uniform3i);
        INSERT_PROC_ADDRESS(gl, Uniform3iv);
        INSERT_PROC_ADDRESS(gl, Uniform4f);
        INSERT_PROC_ADDRESS(gl, Uniform4fv);
        INSERT_PROC_ADDRESS(gl, Uniform4i);
        INSERT_PROC_ADDRESS(gl, Uniform4iv);
        INSERT_PROC_ADDRESS(gl, UniformMatrix2fv);
        INSERT_PROC_ADDRESS(gl, UniformMatrix3fv);
        INSERT_PROC_ADDRESS(gl, UniformMatrix4fv);
        INSERT_PROC_ADDRESS(gl, UseProgram);
        INSERT_PROC_ADDRESS(gl, ValidateProgram);
        INSERT_PROC_ADDRESS(gl, VertexAttrib1f);
        INSERT_PROC_ADDRESS(gl, VertexAttrib1fv);
        INSERT_PROC_ADDRESS(gl, VertexAttrib2f);
        INSERT_PROC_ADDRESS(gl, VertexAttrib2fv);
        INSERT_PROC_ADDRESS(gl, VertexAttrib3f);
        INSERT_PROC_ADDRESS(gl, VertexAttrib3fv);
        INSERT_PROC_ADDRESS(gl, VertexAttrib4f);
        INSERT_PROC_ADDRESS(gl, VertexAttrib4fv);
        INSERT_PROC_ADDRESS(gl, VertexAttribPointer);
        INSERT_PROC_ADDRESS(gl, Viewport);

        // GL_ANGLE_framebuffer_blit
        INSERT_PROC_ADDRESS(gl, BlitFramebufferANGLE);

        // GL_ANGLE_framebuffer_multisample
        INSERT_PROC_ADDRESS(gl, RenderbufferStorageMultisampleANGLE);

        // GL_EXT_discard_framebuffer
        INSERT_PROC_ADDRESS(gl, DiscardFramebufferEXT);

        // GL_NV_fence
        INSERT_PROC_ADDRESS(gl, DeleteFencesNV);
        INSERT_PROC_ADDRESS(gl, GenFencesNV);
        INSERT_PROC_ADDRESS(gl, IsFenceNV);
        INSERT_PROC_ADDRESS(gl, TestFenceNV);
        INSERT_PROC_ADDRESS(gl, GetFenceivNV);
        INSERT_PROC_ADDRESS(gl, FinishFenceNV);
        INSERT_PROC_ADDRESS(gl, SetFenceNV);

        // GL_ANGLE_translated_shader_source
        INSERT_PROC_ADDRESS(gl, GetTranslatedShaderSourceANGLE);

        // GL_EXT_texture_storage
        INSERT_PROC_ADDRESS(gl, TexStorage2DEXT);

        // GL_EXT_robustness
        INSERT_PROC_ADDRESS(gl, GetGraphicsResetStatusEXT);
        INSERT_PROC_ADDRESS(gl, ReadnPixelsEXT);
        INSERT_PROC_ADDRESS(gl, GetnUniformfvEXT);
        INSERT_PROC_ADDRESS(gl, GetnUniformivEXT);

        // GL_EXT_occlusion_query_boolean
        INSERT_PROC_ADDRESS(gl, GenQueriesEXT);
        INSERT_PROC_ADDRESS(gl, DeleteQueriesEXT);
        INSERT_PROC_ADDRESS(gl, IsQueryEXT);
        INSERT_PROC_ADDRESS(gl, BeginQueryEXT);
        INSERT_PROC_ADDRESS(gl, EndQueryEXT);
        INSERT_PROC_ADDRESS(gl, GetQueryivEXT);
        INSERT_PROC_ADDRESS(gl, GetQueryObjectuivEXT);

        // GL_EXT_disjoint_timer_query
        // Commented out functions are needed for GL_EXT_disjoint_timer_query
        // but are pulled in by GL_EXT_occlusion_query_boolean.
        // INSERT_PROC_ADDRESS(gl, GenQueriesEXT);
        // INSERT_PROC_ADDRESS(gl, DeleteQueriesEXT);
        // INSERT_PROC_ADDRESS(gl, IsQueryEXT);
        // INSERT_PROC_ADDRESS(gl, BeginQueryEXT);
        // INSERT_PROC_ADDRESS(gl, EndQueryEXT);
        INSERT_PROC_ADDRESS(gl, QueryCounterEXT);
        // INSERT_PROC_ADDRESS(gl, GetQueryivEXT);
        INSERT_PROC_ADDRESS(gl, GetQueryObjectivEXT);
        // INSERT_PROC_ADDRESS(gl, GetQueryObjectuivEXT);
        INSERT_PROC_ADDRESS(gl, GetQueryObjecti64vEXT);
        INSERT_PROC_ADDRESS(gl, GetQueryObjectui64vEXT);

        // GL_EXT_draw_buffers
        INSERT_PROC_ADDRESS(gl, DrawBuffersEXT);

        // GL_ANGLE_instanced_arrays
        INSERT_PROC_ADDRESS(gl, DrawArraysInstancedANGLE);
        INSERT_PROC_ADDRESS(gl, DrawElementsInstancedANGLE);
        INSERT_PROC_ADDRESS(gl, VertexAttribDivisorANGLE);

        // GL_OES_get_program_binary
        INSERT_PROC_ADDRESS(gl, GetProgramBinaryOES);
        INSERT_PROC_ADDRESS(gl, ProgramBinaryOES);

        // GL_OES_mapbuffer
        INSERT_PROC_ADDRESS(gl, MapBufferOES);
        INSERT_PROC_ADDRESS(gl, UnmapBufferOES);
        INSERT_PROC_ADDRESS(gl, GetBufferPointervOES);

        // GL_EXT_map_buffer_range
        INSERT_PROC_ADDRESS(gl, MapBufferRangeEXT);
        INSERT_PROC_ADDRESS(gl, FlushMappedBufferRangeEXT);

        // GL_EXT_debug_marker
        INSERT_PROC_ADDRESS(gl, InsertEventMarkerEXT);
        INSERT_PROC_ADDRESS(gl, PushGroupMarkerEXT);
        INSERT_PROC_ADDRESS(gl, PopGroupMarkerEXT);

        // GL_OES_EGL_image
        INSERT_PROC_ADDRESS(gl, EGLImageTargetTexture2DOES);
        INSERT_PROC_ADDRESS(gl, EGLImageTargetRenderbufferStorageOES);

        // GL_OES_vertex_array_object
        INSERT_PROC_ADDRESS(gl, BindVertexArrayOES);
        INSERT_PROC_ADDRESS(gl, DeleteVertexArraysOES);
        INSERT_PROC_ADDRESS(gl, GenVertexArraysOES);
        INSERT_PROC_ADDRESS(gl, IsVertexArrayOES);

        // GL_KHR_debug
        INSERT_PROC_ADDRESS(gl, DebugMessageControlKHR);
        INSERT_PROC_ADDRESS(gl, DebugMessageInsertKHR);
        INSERT_PROC_ADDRESS(gl, DebugMessageCallbackKHR);
        INSERT_PROC_ADDRESS(gl, GetDebugMessageLogKHR);
        INSERT_PROC_ADDRESS(gl, PushDebugGroupKHR);
        INSERT_PROC_ADDRESS(gl, PopDebugGroupKHR);
        INSERT_PROC_ADDRESS(gl, ObjectLabelKHR);
        INSERT_PROC_ADDRESS(gl, GetObjectLabelKHR);
        INSERT_PROC_ADDRESS(gl, ObjectPtrLabelKHR);
        INSERT_PROC_ADDRESS(gl, GetObjectPtrLabelKHR);
        INSERT_PROC_ADDRESS(gl, GetPointervKHR);

        // GL_CHROMIUM_bind_uniform_location
        INSERT_PROC_ADDRESS(gl, BindUniformLocationCHROMIUM);

        // GL_CHROMIUM_copy_texture
        INSERT_PROC_ADDRESS(gl, CopyTextureCHROMIUM);
        INSERT_PROC_ADDRESS(gl, CopySubTextureCHROMIUM);

        // GL_CHROMIUM_copy_compressed_texture
        INSERT_PROC_ADDRESS(gl, CompressedCopyTextureCHROMIUM);

        // GL_ANGLE_request_extension
        INSERT_PROC_ADDRESS(gl, RequestExtensionANGLE);

        // GL_ANGLE_robust_client_memory
        INSERT_PROC_ADDRESS(gl, GetBooleanvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetBufferParameterivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetFloatvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetFramebufferAttachmentParameterivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetIntegervRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetProgramivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetRenderbufferParameterivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetShaderivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetTexParameterfvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetTexParameterivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetUniformfvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetUniformivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribfvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribPointervRobustANGLE);
        INSERT_PROC_ADDRESS(gl, ReadPixelsRobustANGLE);
        INSERT_PROC_ADDRESS(gl, TexImage2DRobustANGLE);
        INSERT_PROC_ADDRESS(gl, TexParameterfvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, TexParameterivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, TexSubImage2DRobustANGLE);
        INSERT_PROC_ADDRESS(gl, TexImage3DRobustANGLE);
        INSERT_PROC_ADDRESS(gl, TexSubImage3DRobustANGLE);
        INSERT_PROC_ADDRESS(gl, CompressedTexImage2DRobustANGLE);
        INSERT_PROC_ADDRESS(gl, CompressedTexSubImage2DRobustANGLE);
        INSERT_PROC_ADDRESS(gl, CompressedTexImage3DRobustANGLE);
        INSERT_PROC_ADDRESS(gl, CompressedTexSubImage3DRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetQueryivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetQueryObjectuivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetBufferPointervRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetIntegeri_vRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetInternalformativRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribIivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribIuivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetUniformuivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetActiveUniformBlockivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetInteger64vRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetInteger64i_vRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetBufferParameteri64vRobustANGLE);
        INSERT_PROC_ADDRESS(gl, SamplerParameterivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, SamplerParameterfvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetSamplerParameterivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetSamplerParameterfvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetFramebufferParameterivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetProgramInterfaceivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetBooleani_vRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetMultisamplefvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetTexLevelParameterivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetTexLevelParameterfvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetPointervRobustANGLERobustANGLE);
        INSERT_PROC_ADDRESS(gl, ReadnPixelsRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetnUniformfvRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetnUniformivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetnUniformuivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, TexParameterIivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, TexParameterIuivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetTexParameterIivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetTexParameterIuivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, SamplerParameterIivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, SamplerParameterIuivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetSamplerParameterIivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetSamplerParameterIuivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetQueryObjectivRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetQueryObjecti64vRobustANGLE);
        INSERT_PROC_ADDRESS(gl, GetQueryObjectui64vRobustANGLE);

        // GL_ANGLE_multiview
        INSERT_PROC_ADDRESS(gl, FramebufferTextureMultiviewLayeredANGLE);
        INSERT_PROC_ADDRESS(gl, FramebufferTextureMultiviewSideBySideANGLE);

        // GLES3 core
        INSERT_PROC_ADDRESS(gl, ReadBuffer);
        INSERT_PROC_ADDRESS(gl, DrawRangeElements);
        INSERT_PROC_ADDRESS(gl, TexImage3D);
        INSERT_PROC_ADDRESS(gl, TexSubImage3D);
        INSERT_PROC_ADDRESS(gl, CopyTexSubImage3D);
        INSERT_PROC_ADDRESS(gl, CompressedTexImage3D);
        INSERT_PROC_ADDRESS(gl, CompressedTexSubImage3D);
        INSERT_PROC_ADDRESS(gl, GenQueries);
        INSERT_PROC_ADDRESS(gl, DeleteQueries);
        INSERT_PROC_ADDRESS(gl, IsQuery);
        INSERT_PROC_ADDRESS(gl, BeginQuery);
        INSERT_PROC_ADDRESS(gl, EndQuery);
        INSERT_PROC_ADDRESS(gl, GetQueryiv);
        INSERT_PROC_ADDRESS(gl, GetQueryObjectuiv);
        INSERT_PROC_ADDRESS(gl, UnmapBuffer);
        INSERT_PROC_ADDRESS(gl, GetBufferPointerv);
        INSERT_PROC_ADDRESS(gl, DrawBuffers);
        INSERT_PROC_ADDRESS(gl, UniformMatrix2x3fv);
        INSERT_PROC_ADDRESS(gl, UniformMatrix3x2fv);
        INSERT_PROC_ADDRESS(gl, UniformMatrix2x4fv);
        INSERT_PROC_ADDRESS(gl, UniformMatrix4x2fv);
        INSERT_PROC_ADDRESS(gl, UniformMatrix3x4fv);
        INSERT_PROC_ADDRESS(gl, UniformMatrix4x3fv);
        INSERT_PROC_ADDRESS(gl, BlitFramebuffer);
        INSERT_PROC_ADDRESS(gl, RenderbufferStorageMultisample);
        INSERT_PROC_ADDRESS(gl, FramebufferTextureLayer);
        INSERT_PROC_ADDRESS(gl, MapBufferRange);
        INSERT_PROC_ADDRESS(gl, FlushMappedBufferRange);
        INSERT_PROC_ADDRESS(gl, BindVertexArray);
        INSERT_PROC_ADDRESS(gl, DeleteVertexArrays);
        INSERT_PROC_ADDRESS(gl, GenVertexArrays);
        INSERT_PROC_ADDRESS(gl, IsVertexArray);
        INSERT_PROC_ADDRESS(gl, GetIntegeri_v);
        INSERT_PROC_ADDRESS(gl, BeginTransformFeedback);
        INSERT_PROC_ADDRESS(gl, EndTransformFeedback);
        INSERT_PROC_ADDRESS(gl, BindBufferRange);
        INSERT_PROC_ADDRESS(gl, BindBufferBase);
        INSERT_PROC_ADDRESS(gl, TransformFeedbackVaryings);
        INSERT_PROC_ADDRESS(gl, GetTransformFeedbackVarying);
        INSERT_PROC_ADDRESS(gl, VertexAttribIPointer);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribIiv);
        INSERT_PROC_ADDRESS(gl, GetVertexAttribIuiv);
        INSERT_PROC_ADDRESS(gl, VertexAttribI4i);
        INSERT_PROC_ADDRESS(gl, VertexAttribI4ui);
        INSERT_PROC_ADDRESS(gl, VertexAttribI4iv);
        INSERT_PROC_ADDRESS(gl, VertexAttribI4uiv);
        INSERT_PROC_ADDRESS(gl, GetUniformuiv);
        INSERT_PROC_ADDRESS(gl, GetFragDataLocation);
        INSERT_PROC_ADDRESS(gl, Uniform1ui);
        INSERT_PROC_ADDRESS(gl, Uniform2ui);
        INSERT_PROC_ADDRESS(gl, Uniform3ui);
        INSERT_PROC_ADDRESS(gl, Uniform4ui);
        INSERT_PROC_ADDRESS(gl, Uniform1uiv);
        INSERT_PROC_ADDRESS(gl, Uniform2uiv);
        INSERT_PROC_ADDRESS(gl, Uniform3uiv);
        INSERT_PROC_ADDRESS(gl, Uniform4uiv);
        INSERT_PROC_ADDRESS(gl, ClearBufferiv);
        INSERT_PROC_ADDRESS(gl, ClearBufferuiv);
        INSERT_PROC_ADDRESS(gl, ClearBufferfv);
        INSERT_PROC_ADDRESS(gl, ClearBufferfi);
        INSERT_PROC_ADDRESS(gl, GetStringi);
        INSERT_PROC_ADDRESS(gl, CopyBufferSubData);
        INSERT_PROC_ADDRESS(gl, GetUniformIndices);
        INSERT_PROC_ADDRESS(gl, GetActiveUniformsiv);
        INSERT_PROC_ADDRESS(gl, GetUniformBlockIndex);
        INSERT_PROC_ADDRESS(gl, GetActiveUniformBlockiv);
        INSERT_PROC_ADDRESS(gl, GetActiveUniformBlockName);
        INSERT_PROC_ADDRESS(gl, UniformBlockBinding);
        INSERT_PROC_ADDRESS(gl, DrawArraysInstanced);
        INSERT_PROC_ADDRESS(gl, DrawElementsInstanced);
        INSERT_PROC_ADDRESS(gl, FenceSync);
        INSERT_PROC_ADDRESS(gl, IsSync);
        INSERT_PROC_ADDRESS(gl, DeleteSync);
        INSERT_PROC_ADDRESS(gl, ClientWaitSync);
        INSERT_PROC_ADDRESS(gl, WaitSync);
        INSERT_PROC_ADDRESS(gl, GetInteger64v);
        INSERT_PROC_ADDRESS(gl, GetSynciv);
        INSERT_PROC_ADDRESS(gl, GetInteger64i_v);
        INSERT_PROC_ADDRESS(gl, GetBufferParameteri64v);
        INSERT_PROC_ADDRESS(gl, GenSamplers);
        INSERT_PROC_ADDRESS(gl, DeleteSamplers);
        INSERT_PROC_ADDRESS(gl, IsSampler);
        INSERT_PROC_ADDRESS(gl, BindSampler);
        INSERT_PROC_ADDRESS(gl, SamplerParameteri);
        INSERT_PROC_ADDRESS(gl, SamplerParameteriv);
        INSERT_PROC_ADDRESS(gl, SamplerParameterf);
        INSERT_PROC_ADDRESS(gl, SamplerParameterfv);
        INSERT_PROC_ADDRESS(gl, GetSamplerParameteriv);
        INSERT_PROC_ADDRESS(gl, GetSamplerParameterfv);
        INSERT_PROC_ADDRESS(gl, VertexAttribDivisor);
        INSERT_PROC_ADDRESS(gl, BindTransformFeedback);
        INSERT_PROC_ADDRESS(gl, DeleteTransformFeedbacks);
        INSERT_PROC_ADDRESS(gl, GenTransformFeedbacks);
        INSERT_PROC_ADDRESS(gl, IsTransformFeedback);
        INSERT_PROC_ADDRESS(gl, PauseTransformFeedback);
        INSERT_PROC_ADDRESS(gl, ResumeTransformFeedback);
        INSERT_PROC_ADDRESS(gl, GetProgramBinary);
        INSERT_PROC_ADDRESS(gl, ProgramBinary);
        INSERT_PROC_ADDRESS(gl, ProgramParameteri);
        INSERT_PROC_ADDRESS(gl, InvalidateFramebuffer);
        INSERT_PROC_ADDRESS(gl, InvalidateSubFramebuffer);
        INSERT_PROC_ADDRESS(gl, TexStorage2D);
        INSERT_PROC_ADDRESS(gl, TexStorage3D);
        INSERT_PROC_ADDRESS(gl, GetInternalformativ);

        // GLES31 core
        INSERT_PROC_ADDRESS(gl, DispatchCompute);
        INSERT_PROC_ADDRESS(gl, DispatchComputeIndirect);
        INSERT_PROC_ADDRESS(gl, DrawArraysIndirect);
        INSERT_PROC_ADDRESS(gl, DrawElementsIndirect);
        INSERT_PROC_ADDRESS(gl, FramebufferParameteri);
        INSERT_PROC_ADDRESS(gl, GetFramebufferParameteriv);
        INSERT_PROC_ADDRESS(gl, GetProgramInterfaceiv);
        INSERT_PROC_ADDRESS(gl, GetProgramResourceIndex);
        INSERT_PROC_ADDRESS(gl, GetProgramResourceName);
        INSERT_PROC_ADDRESS(gl, GetProgramResourceiv);
        INSERT_PROC_ADDRESS(gl, GetProgramResourceLocation);
        INSERT_PROC_ADDRESS(gl, UseProgramStages);
        INSERT_PROC_ADDRESS(gl, ActiveShaderProgram);
        INSERT_PROC_ADDRESS(gl, CreateShaderProgramv);
        INSERT_PROC_ADDRESS(gl, BindProgramPipeline);
        INSERT_PROC_ADDRESS(gl, DeleteProgramPipelines);
        INSERT_PROC_ADDRESS(gl, GenProgramPipelines);
        INSERT_PROC_ADDRESS(gl, IsProgramPipeline);
        INSERT_PROC_ADDRESS(gl, GetProgramPipelineiv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform1i);
        INSERT_PROC_ADDRESS(gl, ProgramUniform2i);
        INSERT_PROC_ADDRESS(gl, ProgramUniform3i);
        INSERT_PROC_ADDRESS(gl, ProgramUniform4i);
        INSERT_PROC_ADDRESS(gl, ProgramUniform1ui);
        INSERT_PROC_ADDRESS(gl, ProgramUniform2ui);
        INSERT_PROC_ADDRESS(gl, ProgramUniform3ui);
        INSERT_PROC_ADDRESS(gl, ProgramUniform4ui);
        INSERT_PROC_ADDRESS(gl, ProgramUniform1f);
        INSERT_PROC_ADDRESS(gl, ProgramUniform2f);
        INSERT_PROC_ADDRESS(gl, ProgramUniform3f);
        INSERT_PROC_ADDRESS(gl, ProgramUniform4f);
        INSERT_PROC_ADDRESS(gl, ProgramUniform1iv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform2iv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform3iv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform4iv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform1uiv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform2uiv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform3uiv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform4uiv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform1fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform2fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform3fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniform4fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniformMatrix2fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniformMatrix3fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniformMatrix4fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniformMatrix2x3fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniformMatrix3x2fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniformMatrix2x4fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniformMatrix4x2fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniformMatrix3x4fv);
        INSERT_PROC_ADDRESS(gl, ProgramUniformMatrix4x3fv);
        INSERT_PROC_ADDRESS(gl, ValidateProgramPipeline);
        INSERT_PROC_ADDRESS(gl, GetProgramPipelineInfoLog);
        INSERT_PROC_ADDRESS(gl, BindImageTexture);
        INSERT_PROC_ADDRESS(gl, GetBooleani_v);
        INSERT_PROC_ADDRESS(gl, MemoryBarrier);
        INSERT_PROC_ADDRESS(gl, MemoryBarrierByRegion);
        INSERT_PROC_ADDRESS(gl, TexStorage2DMultisample);
        INSERT_PROC_ADDRESS(gl, GetMultisamplefv);
        INSERT_PROC_ADDRESS(gl, SampleMaski);
        INSERT_PROC_ADDRESS(gl, GetTexLevelParameteriv);
        INSERT_PROC_ADDRESS(gl, GetTexLevelParameterfv);
        INSERT_PROC_ADDRESS(gl, BindVertexBuffer);
        INSERT_PROC_ADDRESS(gl, VertexAttribFormat);
        INSERT_PROC_ADDRESS(gl, VertexAttribIFormat);
        INSERT_PROC_ADDRESS(gl, VertexAttribBinding);
        INSERT_PROC_ADDRESS(gl, VertexBindingDivisor);

        // EGL 1.0
        INSERT_PROC_ADDRESS(egl, ChooseConfig);
        INSERT_PROC_ADDRESS(egl, CopyBuffers);
        INSERT_PROC_ADDRESS(egl, CreateContext);
        INSERT_PROC_ADDRESS(egl, CreatePbufferSurface);
        INSERT_PROC_ADDRESS(egl, CreatePixmapSurface);
        INSERT_PROC_ADDRESS(egl, CreateWindowSurface);
        INSERT_PROC_ADDRESS(egl, DestroyContext);
        INSERT_PROC_ADDRESS(egl, DestroySurface);
        INSERT_PROC_ADDRESS(egl, GetConfigAttrib);
        INSERT_PROC_ADDRESS(egl, GetConfigs);
        INSERT_PROC_ADDRESS(egl, GetCurrentDisplay);
        INSERT_PROC_ADDRESS(egl, GetCurrentSurface);
        INSERT_PROC_ADDRESS(egl, GetDisplay);
        INSERT_PROC_ADDRESS(egl, GetError);
        INSERT_PROC_ADDRESS(egl, GetProcAddress);
        INSERT_PROC_ADDRESS(egl, Initialize);
        INSERT_PROC_ADDRESS(egl, MakeCurrent);
        INSERT_PROC_ADDRESS(egl, QueryContext);
        INSERT_PROC_ADDRESS(egl, QueryString);
        INSERT_PROC_ADDRESS(egl, QuerySurface);
        INSERT_PROC_ADDRESS(egl, SwapBuffers);
        INSERT_PROC_ADDRESS(egl, Terminate);
        INSERT_PROC_ADDRESS(egl, WaitGL);
        INSERT_PROC_ADDRESS(egl, WaitNative);

        // EGL 1.1
        INSERT_PROC_ADDRESS(egl, BindTexImage);
        INSERT_PROC_ADDRESS(egl, ReleaseTexImage);
        INSERT_PROC_ADDRESS(egl, SurfaceAttrib);
        INSERT_PROC_ADDRESS(egl, SwapInterval);

        // EGL 1.2
        INSERT_PROC_ADDRESS(egl, BindAPI);
        INSERT_PROC_ADDRESS(egl, QueryAPI);
        INSERT_PROC_ADDRESS(egl, CreatePbufferFromClientBuffer);
        INSERT_PROC_ADDRESS(egl, ReleaseThread);
        INSERT_PROC_ADDRESS(egl, WaitClient);

        // EGL 1.4
        INSERT_PROC_ADDRESS(egl, GetCurrentContext);

        // EGL 1.5
        INSERT_PROC_ADDRESS(egl, CreateSync);
        INSERT_PROC_ADDRESS(egl, DestroySync);
        INSERT_PROC_ADDRESS(egl, ClientWaitSync);
        INSERT_PROC_ADDRESS(egl, GetSyncAttrib);
        INSERT_PROC_ADDRESS(egl, CreateImage);
        INSERT_PROC_ADDRESS(egl, DestroyImage);
        INSERT_PROC_ADDRESS(egl, GetPlatformDisplay);
        INSERT_PROC_ADDRESS(egl, CreatePlatformWindowSurface);
        INSERT_PROC_ADDRESS(egl, CreatePlatformPixmapSurface);
        INSERT_PROC_ADDRESS(egl, WaitSync);

        // EGL_ANGLE_query_surface_pointer
        INSERT_PROC_ADDRESS(egl, QuerySurfacePointerANGLE);

        // EGL_NV_post_sub_buffer
        INSERT_PROC_ADDRESS(egl, PostSubBufferNV);

        // EGL_EXT_platform_base
        INSERT_PROC_ADDRESS(egl, GetPlatformDisplayEXT);

        // EGL_EXT_device_query
        INSERT_PROC_ADDRESS(egl, QueryDisplayAttribEXT);
        INSERT_PROC_ADDRESS(egl, QueryDeviceAttribEXT);
        INSERT_PROC_ADDRESS(egl, QueryDeviceStringEXT);

        // EGL_KHR_image_base/EGL_KHR_image
        INSERT_PROC_ADDRESS(egl, CreateImageKHR);
        INSERT_PROC_ADDRESS(egl, DestroyImageKHR);

        // EGL_EXT_device_creation
        INSERT_PROC_ADDRESS(egl, CreateDeviceANGLE);
        INSERT_PROC_ADDRESS(egl, ReleaseDeviceANGLE);

        // EGL_KHR_stream
        INSERT_PROC_ADDRESS(egl, CreateStreamKHR);
        INSERT_PROC_ADDRESS(egl, DestroyStreamKHR);
        INSERT_PROC_ADDRESS(egl, StreamAttribKHR);
        INSERT_PROC_ADDRESS(egl, QueryStreamKHR);
        INSERT_PROC_ADDRESS(egl, QueryStreamu64KHR);

        // EGL_KHR_stream_consumer_gltexture
        INSERT_PROC_ADDRESS(egl, StreamConsumerGLTextureExternalKHR);
        INSERT_PROC_ADDRESS(egl, StreamConsumerAcquireKHR);
        INSERT_PROC_ADDRESS(egl, StreamConsumerReleaseKHR);

        // EGL_NV_stream_consumer_gltexture_yuv
        INSERT_PROC_ADDRESS(egl, StreamConsumerGLTextureExternalAttribsNV);

        // EGL_ANGLE_stream_producer_d3d_texture_nv12
        INSERT_PROC_ADDRESS(egl, CreateStreamProducerD3DTextureNV12ANGLE);
        INSERT_PROC_ADDRESS(egl, StreamPostD3DTextureNV12ANGLE);

        // EGL_CHROMIUM_get_sync_values
        INSERT_PROC_ADDRESS(egl, GetSyncValuesCHROMIUM);

        // EGL_EXT_swap_buffers_with_damage
        INSERT_PROC_ADDRESS(egl, SwapBuffersWithDamageEXT);

        // EGL_ANGLE_program_cache_control
        INSERT_PROC_ADDRESS(egl, ProgramCacheGetAttribANGLE);
        INSERT_PROC_ADDRESS(egl, ProgramCacheQueryANGLE);
        INSERT_PROC_ADDRESS(egl, ProgramCachePopulateANGLE);
        INSERT_PROC_ADDRESS(egl, ProgramCacheResizeANGLE);

        // angle::Platform related entry points
        INSERT_PROC_ADDRESS_NO_NS("ANGLEGetDisplayPlatform", ANGLEGetDisplayPlatform);
        INSERT_PROC_ADDRESS_NO_NS("ANGLEResetDisplayPlatform", ANGLEResetDisplayPlatform);

#undef INSERT_PROC_ADDRESS
#undef INSERT_PROC_ADDRESS_NO_NS

        return map;
    };

    static const ProcAddressMap procAddressMap = generateProcAddressMap();

    thread->setError(NoError());
    auto iter = procAddressMap.find(procname);
    return iter != procAddressMap.end() ? iter->second : nullptr;
}
}
